package top.cardone.context.event;

import com.google.common.collect.Lists;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.AnnotationUtils;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.context.annotation.Event;
import top.cardone.context.annotation.Events;

import java.util.List;

/**
 * @author yao hai tao
 * @date 2017/7/1
 */
@Log4j2
public class EventAspect {
    public Object eventAspect(final ProceedingJoinPoint joinPoint) throws Throwable {
        java.lang.reflect.Method method = ((org.aspectj.lang.reflect.MethodSignature) joinPoint.getSignature()).getMethod();

        Event event = AnnotationUtils.findAnnotation(method, Event.class);

        List<Event> eventList = (event == null) ? Lists.newArrayList() : Lists.newArrayList(event);

        Events events = AnnotationUtils.findAnnotation(method, Events.class);

        if (events != null) {
            eventList.addAll(Lists.newArrayList(events.value()));
        }

        event = AnnotationUtils.findAnnotation(joinPoint.getTarget().getClass(), Event.class);

        if (event != null) {
            eventList.add(event);
        }

        events = AnnotationUtils.findAnnotation(joinPoint.getTarget().getClass(), Events.class);

        if (events != null) {
            eventList.addAll(Lists.newArrayList(events.value()));
        }

        if (CollectionUtils.isEmpty(eventList)) {
            return joinPoint.proceed();
        }

        String beforeName = Before.class.getSimpleName() + Event.class.getSimpleName();

        String errorName = "Error" + Event.class.getSimpleName();

        String[] flags = new String[]{joinPoint.getTarget().getClass().getName(), joinPoint.getSignature().getName()};

        try {
            for (int i = eventList.size() - 1; i >= 0; i--) {
                Event itemEvent = eventList.get(i);

                if (SimpleBeforeEvent.class.equals(itemEvent.value())) {
                    eventList.remove(i);

                    ApplicationContextHolder.getApplicationContext().publishEvent(new SimpleBeforeEvent(joinPoint.getTarget(), flags, joinPoint.getArgs(), itemEvent.configs()));
                } else if (StringUtils.endsWith(itemEvent.value().getSimpleName(), beforeName)) {
                    eventList.remove(i);

                    Object applicationEvent = ConstructorUtils.invokeConstructor(itemEvent.value(), new Object[]{joinPoint.getTarget(), flags, joinPoint.getArgs(), itemEvent.configs()}, new Class[]{Object.class, String[].class, Object[].class, String[].class});

                    ApplicationContextHolder.getApplicationContext().publishEvent(applicationEvent);
                }
            }

            Object returnData = joinPoint.proceed();

            for (int i = eventList.size() - 1; i >= 0; i--) {
                Event itemEvent = eventList.get(i);

                if (SimpleEvent.class.equals(itemEvent.value())) {
                    eventList.remove(i);

                    ApplicationContextHolder.getApplicationContext().publishEvent(new SimpleEvent(joinPoint.getTarget(), flags, joinPoint.getArgs(), returnData, itemEvent.configs()));
                } else if (!StringUtils.endsWith(itemEvent.value().getSimpleName(), beforeName) && !StringUtils.endsWith(itemEvent.value().getSimpleName(), errorName)) {
                    eventList.remove(i);

                    Object applicationEvent = ConstructorUtils.invokeConstructor(itemEvent.value(), new Object[]{joinPoint.getTarget(), flags, joinPoint.getArgs(), returnData, itemEvent.configs()}, new Class[]{Object.class, String[].class, Object[].class, Object.class, String[].class});

                    ApplicationContextHolder.getApplicationContext().publishEvent(applicationEvent);
                }
            }

            return returnData;
        } catch (Throwable throwable) {
            for (Event itemEvent : eventList) {
                if (SimpleErrorEvent.class.equals(itemEvent.value())) {
                    ApplicationContextHolder.getApplicationContext().publishEvent(new SimpleErrorEvent(joinPoint.getTarget(), flags, joinPoint.getArgs(), itemEvent.configs(), throwable));
                } else if (StringUtils.endsWith(itemEvent.value().getSimpleName(), errorName)) {
                    Object applicationEvent = ConstructorUtils.invokeConstructor(itemEvent.value(), new Object[]{joinPoint.getTarget(), flags, joinPoint.getArgs(), itemEvent.configs(), throwable}, new Class[]{Object.class, String[].class, Object[].class, String[].class, Throwable.class});

                    ApplicationContextHolder.getApplicationContext().publishEvent(applicationEvent);
                }
            }

            throw throwable;
        }
    }
}